package com.yin.myproject;

import com.google.common.base.Strings;
import com.yin.myproject.core.ScheduleTask;
import com.yin.myproject.core.ScheduledMethodRunnable;
import com.yin.myproject.util.ApplicationContextUtil;
import com.yin.myproject.util.EnhanceReflectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.quartz.CronTrigger;
import org.quartz.Trigger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.util.Assert;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

/**
 * @author Eason
 * @create 2018-04-08 14:17
 **/
public class DynamicTaskManager {
    private DynamicTaskManager() {
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(DynamicTaskManager.class);

    private static final Map<String, ScheduledFuture<?>> SCHEDULE_FUTURES = new ConcurrentHashMap<>();

    /**
     * 取消调度任务
     * @param scheduleKey
     * @param isInterrupted 是否中断正在执行的任务
     */
    public static void cancelTask(String scheduleKey, boolean isInterrupted) {
        if(SCHEDULE_FUTURES.containsKey(scheduleKey)) {
            SCHEDULE_FUTURES.get(scheduleKey).cancel(isInterrupted);
            SCHEDULE_FUTURES.remove(scheduleKey);
        }
    }

    /**
     * 调度本地定时任务
     *
     * @param scheduleTask
     * @throws ClassNotFoundException
     */
    public static void scheduleTask(ScheduleTask scheduleTask) {
        if (scheduleTask == null) {
            return;
        }
        LOGGER.debug("schedule a task: {}", scheduleTask);
        try {

            scheduleTask(scheduleTask.getKey(), getTargetBean(scheduleTask), scheduleTask.getTargetMethod(),
                    scheduleTask.getCronExpression(), scheduleTask.getStartTime(), scheduleTask.getPeriod(),
                    scheduleTask.getParams());
        } catch (Exception e) {
            LOGGER.error("schedule a task failure because {}.", e.getMessage(), e);
        }
    }

    /**
     * 清除本地多余任务，清理不包含在existsTaskName的任务
     * @param existsTaskName 当前调度服务负责的任务集合
     */
    public static void clearLocalTask(List<String> existsTaskName) {
        for (String name : SCHEDULE_FUTURES.keySet()) {
            if (!existsTaskName.contains(name)) {
                SCHEDULE_FUTURES.get(name).cancel(true);
                SCHEDULE_FUTURES.remove(name);
            }
        }
    }

    /**
     * 启动定时任务 支持： <br/>
     * <ol>
     * <li>cron时间表达式，立即执行</li>
     * <li>startTime + period,指定时间，定时进行</li>
     * <li>period，定时进行，立即开始</li>
     * <li>startTime，指定时间执行</li>
     * <ol>
     * <br/>
     *
     * @param scheduleKey
     * @param targetBean
     * @param targetMethod
     * @param cronExpression
     * @param startTime
     * @param period
     * @param params
     */
    private static void scheduleTask(String scheduleKey, Object targetBean, String targetMethod, String cronExpression,
                                     Date startTime, long period, Object... params) {

        try {
            ScheduledFuture<?> scheduledFuture = null;
            ScheduledMethodRunnable scheduledMethodRunnable = buildScheduledRunnable(scheduleKey, targetBean,
                    targetMethod, params);
            if (scheduledMethodRunnable != null) {
                scheduledFuture = SCHEDULE_FUTURES.get(scheduleKey);
                if (scheduledFuture == null) {
                    if (StringUtils.isNotEmpty(cronExpression)) {
                        Trigger trigger = new CronTrigger(cronExpression);
                        scheduledFuture = ConsoleManager.getScheduleManager()
                                .schedule(scheduledMethodRunnable, trigger);
                    } else if (startTime != null) {
                        if (period > 0) {
                            scheduledFuture = ConsoleManager.getScheduleManager().scheduleAtFixedRate(
                                    scheduledMethodRunnable, startTime, period);
                        } else {
                            scheduledFuture = ConsoleManager.getScheduleManager().schedule(scheduledMethodRunnable,
                                    startTime);
                        }
                    } else if (period > 0) {
                        scheduledFuture = ConsoleManager.getScheduleManager().scheduleAtFixedRate(
                                scheduledMethodRunnable, period);
                    }
                    SCHEDULE_FUTURES.put(scheduleKey, scheduledFuture);
                    LOGGER.debug("Building new schedule task, target bean " + targetBean + " target method "
                            + targetMethod + ".");
                }
            } else {
                LOGGER.debug("Bean name is not exists.");
            }
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
    }

    /**
     * 封装任务对象
     *
     * @param scheduleKey
     * @param targetBean
     * @param targetMethod
     * @param params
     * @return
     */
    @SuppressWarnings("unused")
    private static ScheduledMethodRunnable buildScheduledRunnable(String scheduleKey, String targetBean,
                                                                  String targetMethod, Object... params) {
        Object bean = null;
        ScheduledMethodRunnable scheduledMethodRunnable = null;
        try {
            bean = ApplicationContextUtil.getBeanByName(targetBean);
            scheduledMethodRunnable = _buildScheduledRunnable(scheduleKey, bean, targetMethod, params);
        } catch (Exception e) {
            LOGGER.debug(e.getLocalizedMessage(), e);
        }
        return scheduledMethodRunnable;
    }

    /**
     *
     * @param scheduleKey
     * @param bean
     * @param targetMethod
     * @param params
     * @return
     */
    private static ScheduledMethodRunnable buildScheduledRunnable(String scheduleKey, Object bean, String targetMethod,
                                                                  Object... params) {
        ScheduledMethodRunnable scheduledMethodRunnable = null;
        try {
            scheduledMethodRunnable = _buildScheduledRunnable(scheduleKey, bean, targetMethod, params);
        } catch (Exception e) {
            LOGGER.debug(e.getLocalizedMessage(), e);
        }
        return scheduledMethodRunnable;
    }

    /**
     *
     * @param scheduleKey
     * @param bean
     * @param targetMethod
     * @param params
     * @return
     * @throws Exception
     */
    private static ScheduledMethodRunnable _buildScheduledRunnable(String scheduleKey, Object bean,
                                                                   String targetMethod, Object... params) {

        Assert.notNull(bean, "target object must not be null");
        Assert.hasLength(targetMethod, "Method name must not be empty");

        Method method;
        ScheduledMethodRunnable scheduledMethodRunnable;

        Class<?> clazz;
        if (AopUtils.isAopProxy(bean)) {
            clazz = AopProxyUtils.ultimateTargetClass(bean);
        } else {
            clazz = bean.getClass();
        }

        if (params != null) {
            Class<?>[] paramClazz = new Class<?>[params.length];
            for (int i = 0; i < params.length; i++) {
                paramClazz[i] = params[i].getClass();
            }
//            method = ReflectionUtils.findMethod(clazz, targetMethod, paramClazz);
            method = EnhanceReflectionUtils.findMethod(clazz, targetMethod, paramClazz);
        } else {
//            method = ReflectionUtils.findMethod(clazz, targetMethod);
            method = EnhanceReflectionUtils.findMethod(clazz, targetMethod);
        }

        Assert.notNull(method, "can not find method named " + targetMethod);
        scheduledMethodRunnable = new ScheduledMethodRunnable(scheduleKey, bean, method, params);
        return scheduledMethodRunnable;
    }

    /**
     * 获取任务执行实例
     * @param task
     * @return
     * @throws ClassNotFoundException
     */
    private static Object getTargetBean(ScheduleTask task) throws ClassNotFoundException {
        if(!Strings.isNullOrEmpty(task.getTargetBean())) {
            return ApplicationContextUtil.getBeanByName(task.getTargetBean());
        } else if(!Strings.isNullOrEmpty(task.getTargetClazz())) {
            return ApplicationContextUtil.getBean(Class.forName(task.getTargetClazz()));
        }
        throw new RuntimeException("ERROR: task TargetBean and TargetClazz is null.");
    }
}
